﻿using System;
using System.Collections.Generic;
using System.Reflection;
using System.Reflection.Emit;
using System.Threading;
using MugenInjection.Delegates;
using MugenInjection.Infrastructure;
using MugenInjection.Interception.Infrastructure;
using MugenInjection.Interception.Interface;

namespace MugenInjection.Interception.Proxy
{
    internal class DefaultProxyBuilder : IProxyBuilder
    {
        #region Nested type

        private struct CreatedProxy
        {
            public IProxyCollection ProxyCollection;
            public Type ProxyType;
        }

        private struct ProxyFields
        {
            public FieldInfo ExternalField;
            public FieldInfo InternalField;
        }

        private struct CachedTypes
        {
            #region Fields

            private readonly int _hash;

            #endregion

            #region Constructor

            /// <summary>
            /// Initializes a new instance of the <see cref="CachedTypes"/> class.
            /// </summary>
            public CachedTypes(int hash)
            {
                _hash = hash;
            }

            #endregion

            #region Method

            private static int GetHash(IList<Type> types)
            {
                int hash = 0;
                unchecked
                {
                    for (int i = 0; i < types.Count; i++)
                    {
                        Type type = types[i];
                        hash += type.GetHashCode();
                    }
                }
                return hash;
            }

            #endregion

            #region Equality members

            /// <summary>
            /// Indicates whether this instance and a specified object are equal.
            /// </summary>
            /// <returns>
            /// true if <paramref name="obj"/> and this instance are the same type and represent the same value; otherwise, false.
            /// </returns>
            /// <param name="obj">Another object to compare to. </param><filterpriority>2</filterpriority>
            public override bool Equals(object obj)
            {
                if (ReferenceEquals(null, obj)) return false;
                return obj is CachedTypes && ((CachedTypes) obj)._hash == _hash;
            }

            public static bool operator ==(CachedTypes left, CachedTypes right)
            {
                return left.Equals(right);
            }

            public static bool operator !=(CachedTypes left, CachedTypes right)
            {
                return !left.Equals(right);
            }

            /// <summary>
            /// Returns the hash code for this instance.
            /// </summary>
            /// <returns>
            /// A 32-bit signed integer that is the hash code for this instance.
            /// </returns>
            /// <filterpriority>2</filterpriority>
            public override int GetHashCode()
            {
                return _hash;
            }

            #endregion
        }

        #endregion

        #region Fields

        private int _counter;

        private static readonly ConstructorInfo ActionConstructor =
            typeof (Action).GetConstructor(new[] {typeof (object), typeof (IntPtr)});

        private const string ProxyFieldName = "<>proxycollection";
        private static readonly Type ProxyFieldType = typeof (IProxyCollection);
        private static readonly Type[] BaseContructorParams = new[] {ProxyFieldType};

        private static readonly Dictionary<CachedTypes, CreatedProxy> CreatedProxies =
            new Dictionary<CachedTypes, CreatedProxy>();

        #endregion

        #region Implementation of IProxyBuilder

        /// <summary>
        /// Gets the name of the parameter for the <see cref="IProxyCollection"/> used in a constructor.
        /// </summary>
        public string ConstructorParameterName
        {
            get { return "<>___proxyCollectionParameter___<>"; }
        }

        /// <summary>
        /// Gets or sets a module that uses a proxy.
        /// </summary>
        public ModuleBuilder DynamicModule { get; set; }

        /// <summary>
        /// Creates a proxy type.
        /// </summary>
        /// <param name="baseType">The specified base type for proxy.</param>
        /// <param name="interfaces">Ths specified interfaces for include to proxy.</param>
        /// <param name="proxyCollection">The result <see cref="IProxyCollection"/>.</param>
        /// <returns>A proxy type.</returns>
        public Type CreateProxy(Type baseType, Type[] interfaces, out IProxyCollection proxyCollection)
        {
            ProxyUtils.AssertValidType(baseType);
            ProxyUtils.AssertValidType(interfaces);
            CreatedProxy createdProxy = CreateProxy(baseType, interfaces);
            proxyCollection = createdProxy.ProxyCollection;
            return createdProxy.ProxyType;
        }

        /// <summary>
        /// Gets the proxy constructor types match the original types.
        /// </summary>
        /// <param name="originalTypes">The specified original types.</param>
        /// <returns>The proxy types.</returns>
        public Type[] GetProxyConstructorTypes(Type[] originalTypes)
        {
            var types = new Type[originalTypes.Length + 1];
            Array.Copy(originalTypes, types, originalTypes.Length);
            types[originalTypes.Length + 1] = typeof (IProxyCollection);
            return types;
        }

        #endregion

        #region Method

        private CreatedProxy CreateProxy(Type service, Type[] interfaces)
        {
            lock (CreatedProxies)
            {
                //Get all interfaces
                var listInterfaces = new List<Type>();
                foreach (Type @interface in interfaces)
                {
                    ProxyUtils.GetAllInterfaces(listInterfaces, @interface);
                }
                interfaces = listInterfaces.ToArray();

                CachedTypes proxyName = GetCachedType(service, interfaces);
                CreatedProxy createdProxy;
                if (!CreatedProxies.TryGetValue(proxyName, out createdProxy))
                {
                    var proxyCollection = new ProxyCollection();
                    TypeBuilder typeBuilder = DynamicModule.DefineType(GenerateProxyName(),
                                                                       TypeAttributes.Public | TypeAttributes.Sealed |
                                                                       TypeAttributes.Class, service, interfaces);
                    ProxyFields defineFields = DefineFields(typeBuilder);

#if NETFX_CORE
                    var constructors = service.GetTypeInfo().DeclaredConstructors;
#else
                    ConstructorInfo[] constructors =
                        service.GetConstructors(BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic |
                                                BindingFlags.Default);
#endif
                    foreach (ConstructorInfo constructorInfo in constructors)
                    {
                        //Skip static constructor.
                        if (IsHaveMethodAttribue(constructorInfo.Attributes, MethodAttributes.Static)) continue;
                        //if is internal and not protected method.
                        if (constructorInfo.IsAssembly && !constructorInfo.IsFamilyAndAssembly) continue;
                        //if is internal and not accessible for assembly.
#if NETFX_CORE
                        if (constructorInfo.IsAssembly && !ProxyUtils.IsInternalToProxy(constructorInfo.DeclaringType.GetTypeInfo().Assembly)) continue;
#else
                        if (constructorInfo.IsAssembly &&
                            !ProxyUtils.IsInternalToProxy(constructorInfo.DeclaringType.Assembly)) continue;
#endif
                        AddConstructor(typeBuilder, defineFields, constructorInfo);
                    }
                    ImplementMethods(typeBuilder, service, defineFields, proxyCollection);
                    for (int i = 0; i < interfaces.Length; i++)
                    {
                        Type @interface = interfaces[i];
                        ImplementMethods(typeBuilder, @interface, defineFields, proxyCollection);
                    }
                    createdProxy = new CreatedProxy
                                       {
#if NETFX_CORE
                        ProxyType = typeBuilder.CreateTypeInfo().AsType(),
#else
                                           ProxyType = typeBuilder.CreateType(),
#endif
                                           ProxyCollection = proxyCollection
                                       };
                    CreatedProxies.Add(proxyName, createdProxy);
                }
                return createdProxy;
            }
        }

        private static ProxyFields DefineFields(TypeBuilder typeBuilder)
        {
            FieldBuilder localField = typeBuilder.DefineField(ProxyFieldName, ProxyFieldType,
                                                              FieldAttributes.Private | FieldAttributes.InitOnly);
            FieldBuilder externalField = typeBuilder.DefineField(ProxyFieldName + "External", ProxyFieldType,
                                                                 FieldAttributes.Private | FieldAttributes.InitOnly);
            return new ProxyFields {ExternalField = externalField, InternalField = localField};
        }

        private static CachedTypes GetCachedType(Type baseType, IList<Type> service)
        {
            int hash = baseType.GetHashCode();
            unchecked
            {
                for (int index = 0; index < service.Count; index++)
                {
                    Type type = service[index];
                    hash += type.GetHashCode();
                }
            }
            return new CachedTypes(hash);
        }

        private static string GenerateProxyName()
        {
            return string.Format("MugenInjectionProxy_Impl_{0}", Guid.NewGuid().ToString("N"));
        }

        private void AddConstructor(TypeBuilder typeBuilder, ProxyFields proxyFields, ConstructorInfo constructorInfo)
        {
            if (constructorInfo.IsPrivate) return;
            ParameterInfo[] parameterInfos = constructorInfo.GetParameters();
            var constrParams = new Type[parameterInfos.Length + 1];
            for (int i = 0; i < parameterInfos.Length; i++)
            {
                constrParams[i] = parameterInfos[i].ParameterType;
            }
            constrParams[parameterInfos.Length] = BaseContructorParams[0];
            MethodAttributes methodAttributes = constructorInfo.Attributes;
#if NETFX_CORE
            if (constructorInfo.DeclaringType.GetTypeInfo().IsAbstract &&
                (IsHaveMethodAttribue(constructorInfo.Attributes, MethodAttributes.Family) || IsHaveMethodAttribue(constructorInfo.Attributes, MethodAttributes.FamORAssem)))
#else
            if (constructorInfo.DeclaringType.IsAbstract &&
                (IsHaveMethodAttribue(constructorInfo.Attributes, MethodAttributes.Family) ||
                 IsHaveMethodAttribue(constructorInfo.Attributes, MethodAttributes.FamORAssem)))
#endif

                methodAttributes = MethodAttributes.Public;

            ConstructorBuilder constructorBuilder = typeBuilder.DefineConstructor(methodAttributes,
                                                                                  constructorInfo.CallingConvention,
                                                                                  constrParams);

            int parameterIndex = 1;
            for (int i = 0; i < parameterInfos.Length; i++)
            {
                constructorBuilder.DefineParameter(parameterIndex, parameterInfos[i].Attributes, parameterInfos[i].Name);
                parameterIndex++;
            }
            constructorBuilder.DefineParameter(parameterIndex, ParameterAttributes.None, ConstructorParameterName);

            ILGenerator ilGenerator = constructorBuilder.GetILGenerator();

            //Set external fields
            ilGenerator.Emit(OpCodes.Ldarg_0);
            ilGenerator.Emit(OpCodes.Ldarg_S, (byte) parameterInfos.Length + 1);
            ilGenerator.Emit(OpCodes.Stfld, proxyFields.ExternalField);

            //Create local fields
            ilGenerator.Emit(OpCodes.Ldarg_0);
            ilGenerator.Emit(OpCodes.Ldarg_S, (byte) parameterInfos.Length + 1);
            ilGenerator.Emit(OpCodes.Callvirt, typeof (IProxyCollection).GetMethod("Clone"));
            ilGenerator.Emit(OpCodes.Stfld, proxyFields.InternalField);

            for (int i = 0; i < parameterInfos.Length + 1; i++)
            {
                ilGenerator.Emit(OpCodes.Ldarg_S, i);
            }
            ilGenerator.Emit(OpCodes.Call, constructorInfo);
            ilGenerator.Emit(OpCodes.Ret);
        }

        /// <summary>
        /// Implement all virtual or abstract method in specified <see cref="service"/>.
        /// </summary>
        /// <param name="typeBuilder"></param>
        /// <param name="service"></param>
        /// <param name="proxyFields"></param>
        /// <param name="proxies"></param>
        private void ImplementMethods(TypeBuilder typeBuilder, Type service, ProxyFields proxyFields,
                                      IProxyCollection proxies)
        {
#if NETFX_CORE
            var methodInfos = service.GetMethods();
#else
            MethodInfo[] methodInfos =
                service.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic |
                                   BindingFlags.Default);
#endif
            int fieldCount = 0;
            foreach (MethodInfo methodInfo in methodInfos)
            {
                //if not virtual or abstract
                if (!methodInfo.IsVirtual && !methodInfo.IsAbstract) continue;
                //if is final or finalize method
                if (methodInfo.IsFinal || methodInfo.Name.Equals("Finalize")) continue;
                //if is internal and not accessible for assembly or if is internal and not protected method.
#if NETFX_CORE
                if (!methodInfo.IsFamilyAndAssembly && methodInfo.IsAssembly && !ProxyUtils.IsInternalToProxy(methodInfo.DeclaringType.GetTypeInfo().Assembly)) continue;
#else
                if (!methodInfo.IsFamilyAndAssembly && methodInfo.IsAssembly &&
                    !ProxyUtils.IsInternalToProxy(methodInfo.DeclaringType.Assembly)) continue;
#endif
                ParameterInfo[] parameterInfos = methodInfo.GetParameters();
                var inputParams = new Type[parameterInfos.Length];
                for (int i = 0; i < parameterInfos.Length; i++)
                {
                    inputParams[i] = parameterInfos[i].ParameterType;
                }

                MethodAttributes methodAttributes = MethodAttributes.Public | MethodAttributes.Final |
                                                    MethodAttributes.Virtual | MethodAttributes.HideBySig;
                string methodName = methodInfo.Name;
#if NETFX_CORE
                if (service.GetTypeInfo().IsInterface)
#else
                if (service.IsInterface)
#endif
                {
                    methodAttributes = MethodAttributes.Private | MethodAttributes.HideBySig |
                                       MethodAttributes.NewSlot | MethodAttributes.Virtual |
                                       MethodAttributes.Final;
                    methodName = string.Format("{0}.{1}", service.Name, methodName);
                }


                MethodBuilder methodBuilder = typeBuilder.DefineMethod(methodName, methodAttributes,
                                                                       CallingConventions.HasThis,
                                                                       methodInfo.ReturnType, inputParams);
#if NETFX_CORE
                var token = methodBuilder.GetHashCode();
#else
                int token = methodBuilder.GetToken().Token;
#endif
                proxies.Add(new MethodProxy {MetadataToken = token, Member = methodInfo});
                typeBuilder.DefineMethodOverride(methodBuilder, methodInfo);
                for (int i = 0; i < parameterInfos.Length; i++)
                {
                    methodBuilder.DefineParameter(i + 1, parameterInfos[i].Attributes, parameterInfos[i].Name);
                }


                ILGenerator ilGenerator = methodBuilder.GetILGenerator();
                LocalBuilder inputParamsLoc = ilGenerator.DeclareLocal(typeof (object[]));
                Label isNotFirstCall = ilGenerator.DefineLabel();

                /*Load method internal proxyBase, and assign input parameters*/
                LocalBuilder internalProxyLoc = FindProxy(ilGenerator, proxyFields.InternalField, typeof (MethodProxy),
                                                          token);

                ilGenerator.Emit(OpCodes.Ldloc, internalProxyLoc);
                ilGenerator.Emit(OpCodes.Callvirt, MethodProxy.InputParametersProperty.GetGetMethod(true));
                ilGenerator.Emit(OpCodes.Stloc_S, inputParamsLoc);

                ilGenerator.Emit(OpCodes.Ldloc, internalProxyLoc);
                ilGenerator.Emit(OpCodes.Callvirt, MethodProxy.InputParametersProperty.GetGetMethod(true));

                //if is not first call goto isNotFirstCall
                ilGenerator.Emit(OpCodes.Brtrue_S, isNotFirstCall);


                /*Create input params array.*/
                ilGenerator.Emit(OpCodes.Ldc_I4, parameterInfos.Length);
                ilGenerator.Emit(OpCodes.Newarr, typeof (object));
                ilGenerator.Emit(OpCodes.Stloc_S, inputParamsLoc);

                //if is not first call
                ilGenerator.MarkLabel(isNotFirstCall);

                for (int i = 0; i < parameterInfos.Length; i++)
                {
                    Type parameterType = parameterInfos[i].ParameterType;
                    ilGenerator.Emit(OpCodes.Ldloc, inputParamsLoc);
                    ilGenerator.Emit(OpCodes.Ldc_I4, i);
                    ilGenerator.Emit(OpCodes.Ldarg, i + 1);
                    EmitReflectionAccessProvider.DereferenceIfNeed(parameterType, ilGenerator);
                    EmitReflectionAccessProvider.BoxIfNeed(parameterType, ilGenerator);
                    ilGenerator.Emit(OpCodes.Stelem_Ref);
                }
                /**/

                /*Load method proxyBase, and assign input parameters*/
                LocalBuilder externalProxyLoc = FindProxy(ilGenerator, proxyFields.ExternalField, typeof (MethodProxy),
                                                          token);
                ilGenerator.Emit(OpCodes.Ldloc, externalProxyLoc);
                ilGenerator.Emit(OpCodes.Ldloc, inputParamsLoc);
                ilGenerator.Emit(OpCodes.Callvirt, MethodProxy.InputParametersProperty.GetSetMethod(true));

                ilGenerator.Emit(OpCodes.Ldloc, externalProxyLoc);
                ilGenerator.Emit(OpCodes.Ldarg_0);
                ilGenerator.Emit(OpCodes.Callvirt, MethodProxy.InstanceProperty.GetSetMethod(true));
                /**/

                /*If method non abstract, implement call base method*/
                if (!methodInfo.IsAbstract)
                {
                    FieldBuilder methodProxyField = null;
                    FieldBuilder parametersField;
                    FieldBuilder parentClass;
                    MethodBuilder invokeMethod;

                    TypeBuilder nestedClass = DeclareNestedClass(typeBuilder, methodBuilder, methodInfo, inputParams,
                                                                 out parametersField, out parentClass, out invokeMethod,
                                                                 (builder, generator) =>
                                                                     {
                                                                         //If have return value create field for MethodProxy, and set ReturnValue after call method.
                                                                         if (methodInfo.ReturnType == typeof (void))
                                                                             return;
                                                                         methodProxyField =
                                                                             builder.DefineField("<>methodProxy",
                                                                                                 typeof (MethodProxy),
                                                                                                 FieldAttributes.Public);
                                                                         LocalBuilder declareLocal =
                                                                             generator.DeclareLocal(
                                                                                 methodInfo.ReturnType);
                                                                         generator.Emit(OpCodes.Stloc_S, declareLocal);
                                                                         generator.Emit(OpCodes.Ldarg_0);
                                                                         generator.Emit(OpCodes.Ldfld, methodProxyField);
                                                                         generator.Emit(OpCodes.Ldloc, declareLocal);
                                                                         EmitReflectionAccessProvider.BoxIfNeed(methodInfo.ReturnType, generator);
                                                                         generator.Emit(OpCodes.Callvirt,
                                                                                        MethodProxy.ReturnValueProperty.
                                                                                            GetSetMethod(true));
                                                                     });
                    ConstructorBuilder defaultConstructor = nestedClass.DefineDefaultConstructor(MethodAttributes.Public);

#if NETFX_CORE
                    var nestedClassField = typeBuilder.DefineField("<>_field" + fieldCount,
                                                                   nestedClass.AsType(), FieldAttributes.Private);
#else
                    FieldBuilder nestedClassField = typeBuilder.DefineField("<>_field" + fieldCount,
                                                                            nestedClass, FieldAttributes.Private);
#endif
                    fieldCount++;
                    Label ifNotNull = ilGenerator.DefineLabel();

                    ilGenerator.Emit(OpCodes.Ldarg_0);
                    ilGenerator.Emit(OpCodes.Ldfld, nestedClassField);
                    ilGenerator.Emit(OpCodes.Brtrue_S, ifNotNull);

                    ilGenerator.Emit(OpCodes.Ldarg_0);
                    ilGenerator.Emit(OpCodes.Newobj, defaultConstructor);
                    ilGenerator.Emit(OpCodes.Stfld, nestedClassField);

                    //If have return value set methodProxy from nestedClass.
                    if (methodInfo.ReturnType != typeof (void))
                    {
                        ilGenerator.Emit(OpCodes.Ldarg_0);
                        ilGenerator.Emit(OpCodes.Ldfld, nestedClassField);
                        ilGenerator.Emit(OpCodes.Ldloc, externalProxyLoc);
                        ilGenerator.Emit(OpCodes.Stfld, methodProxyField);
                    }

                    ilGenerator.Emit(OpCodes.Ldarg_0);
                    ilGenerator.Emit(OpCodes.Ldfld, nestedClassField);
                    ilGenerator.Emit(OpCodes.Ldarg_0);
                    ilGenerator.Emit(OpCodes.Stfld, parentClass);


                    ilGenerator.Emit(OpCodes.Ldloc, internalProxyLoc);
                    ilGenerator.Emit(OpCodes.Ldarg_0);
                    ilGenerator.Emit(OpCodes.Ldfld, nestedClassField);
                    ilGenerator.Emit(OpCodes.Ldftn, invokeMethod);
                    ilGenerator.Emit(OpCodes.Newobj, ActionConstructor);
                    ilGenerator.Emit(OpCodes.Callvirt, MethodProxy.MethodProperty.GetSetMethod(true));

                    ilGenerator.Emit(OpCodes.Ldarg_0);
                    ilGenerator.Emit(OpCodes.Ldfld, nestedClassField);
                    ilGenerator.Emit(OpCodes.Ldloc, inputParamsLoc);
                    ilGenerator.Emit(OpCodes.Stfld, parametersField);


                    ilGenerator.MarkLabel(ifNotNull);
#if NETFX_CORE
                    nestedClass.CreateTypeInfo();
#else
                    nestedClass.CreateType();
#endif
                }
                /**/

                //Assign external proxyBase
                ilGenerator.Emit(OpCodes.Ldloc, externalProxyLoc);
                ilGenerator.Emit(OpCodes.Ldloc, internalProxyLoc);
                ilGenerator.Emit(OpCodes.Callvirt, MethodProxy.MethodProperty.GetGetMethod(true));
                ilGenerator.Emit(OpCodes.Callvirt, MethodProxy.MethodProperty.GetSetMethod(true));


                /*Call OnInvoke method from MethodProxy.*/
                ilGenerator.Emit(OpCodes.Ldloc, externalProxyLoc);
                ilGenerator.Emit(OpCodes.Callvirt, MethodProxy.OnInvokeProperty.GetGetMethod(true));
                ilGenerator.Emit(OpCodes.Callvirt, typeof (Action).GetMethod("Invoke"));
                /**/

                /*Convert ref and out params from reference.*/
                for (int i = 0; i < parameterInfos.Length; i++)
                {
                    Type parameterType = parameterInfos[i].ParameterType;
                    if (!parameterType.IsByRef) continue;
                    ilGenerator.Emit(OpCodes.Ldarg_S, i + 1);
                    ilGenerator.Emit(OpCodes.Ldloc, inputParamsLoc);
                    ilGenerator.Emit(OpCodes.Ldc_I4, i);
                    ilGenerator.Emit(OpCodes.Ldelem_Ref);
                    EmitReflectionAccessProvider.UnboxOrCast(ilGenerator, parameterType);
                    EmitReflectionAccessProvider.ReferenceIfNeed(parameterType, ilGenerator);
                }
                /**/

                /*If have return value, put in stack ReturnValue, from MethodProxy.*/
                if (methodInfo.ReturnType != typeof (void))
                {
                    ilGenerator.Emit(OpCodes.Ldloc, externalProxyLoc);
                    ilGenerator.Emit(OpCodes.Callvirt, MethodProxy.ReturnValueProperty.GetGetMethod(true));
                    EmitReflectionAccessProvider.UnboxOrCast(ilGenerator, methodInfo.ReturnType);
                }
                /**/

                /*Set all parameters to null*/
                ilGenerator.Emit(OpCodes.Ldloc, externalProxyLoc);
                ilGenerator.Emit(OpCodes.Ldnull);
                ilGenerator.Emit(OpCodes.Callvirt, MethodProxy.ReturnValueProperty.GetSetMethod(true));

                ilGenerator.Emit(OpCodes.Ldloc, externalProxyLoc);
                ilGenerator.Emit(OpCodes.Ldnull);
                ilGenerator.Emit(OpCodes.Callvirt, MethodProxy.InputParametersProperty.GetSetMethod(true));

                ilGenerator.Emit(OpCodes.Ldloc, externalProxyLoc);
                ilGenerator.Emit(OpCodes.Ldnull);
                ilGenerator.Emit(OpCodes.Callvirt, MethodProxy.MethodProperty.GetSetMethod(true));

                ilGenerator.Emit(OpCodes.Ldloc, externalProxyLoc);
                ilGenerator.Emit(OpCodes.Ldnull);
                ilGenerator.Emit(OpCodes.Callvirt, MethodProxy.InstanceProperty.GetSetMethod(true));
                /**/

                ilGenerator.Emit(OpCodes.Ret);
            }
        }

        private TypeBuilder DeclareNestedClass(TypeBuilder typeBuilder, MethodBuilder method, MethodInfo baseMethod,
                                               Type[] objectTypes,
                                               out FieldBuilder parametersField, out FieldBuilder parentClass,
                                               out MethodBuilder invokeMethod,
                                               Action<TypeBuilder, ILGenerator> afterMethodCall)
        {
            /*Define nested class, and fields*/
            TypeBuilder nestedType =
                typeBuilder.DefineNestedType(
                    "<>nested_class_for_method_" + method.Name + Interlocked.Increment(ref _counter),
                    TypeAttributes.NestedPrivate | TypeAttributes.Class | TypeAttributes.Sealed);
            parametersField = nestedType.DefineField("<>parametersField", typeof (object[]), FieldAttributes.Public);
#if NETFX_CORE
            parentClass = nestedType.DefineField("<>parentClassField", typeBuilder.AsType(), FieldAttributes.Public);
#else
            parentClass = nestedType.DefineField("<>parentClassField", typeBuilder, FieldAttributes.Public);
#endif
            /**/

            /*Define method for direct call from nested class*/
            MethodBuilder methodBuilder = typeBuilder.DefineMethod("<>" + method.Name + "_invokeBase",
                                                                   MethodAttributes.Private,
                                                                   CallingConventions.Standard, method.ReturnType,
                                                                   objectTypes);
            ILGenerator ilGenerator = methodBuilder.GetILGenerator();
            ilGenerator.Emit(OpCodes.Ldarg_0);
            for (int i = 0; i < objectTypes.Length; i++)
            {
                ilGenerator.Emit(OpCodes.Ldarg_S, i + 1);
            }
            ilGenerator.Emit(OpCodes.Call, baseMethod);
            ilGenerator.Emit(OpCodes.Ret);
            /**/

            /*Define invoke method in nested class, for associate it with Action.*/
            invokeMethod = nestedType.DefineMethod("Invoke", MethodAttributes.Public, CallingConventions.Standard);
            ILGenerator generator = invokeMethod.GetILGenerator();

            /*Prepare parameters for InvokeBase method.*/
            if (objectTypes.Length != 0)
            {
                for (int i = 0; i < objectTypes.Length; i++)
                {
                    Type type = objectTypes[i];
                    generator.Emit(OpCodes.Ldarg_0);
                    generator.Emit(OpCodes.Ldfld, parametersField);
                    generator.Emit(OpCodes.Ldc_I4, i);

                    Type elementType = type.GetElementType() ?? type;
                    generator.DeclareLocal(elementType);
                    generator.Emit(OpCodes.Ldelem_Ref);
                    EmitReflectionAccessProvider.UnboxOrCast(generator, elementType);
                    generator.Emit(OpCodes.Stloc_S, i);
                }
            }
            /**/

            //Load parent class.
            generator.Emit(OpCodes.Ldarg_0);
            generator.Emit(OpCodes.Ldfld, parentClass);

            //Load all parameter for call InvokeBase.
            for (int i = 0; i < objectTypes.Length; i++)
            {
                Type type = objectTypes[i];
                generator.Emit(type.IsByRef ? OpCodes.Ldloca_S : OpCodes.Ldloc_S, i);
            }

            generator.Emit(OpCodes.Callvirt, methodBuilder);
            afterMethodCall(nestedType, generator);

            /*Convert ref and out params from reference.*/
            for (int i = 0; i < objectTypes.Length; i++)
            {
                Type parameterType = objectTypes[i];
                if (!parameterType.IsByRef) continue;
                generator.Emit(OpCodes.Ldarg_0);
                generator.Emit(OpCodes.Ldfld, parametersField);
                generator.Emit(OpCodes.Ldc_I4, i);
                generator.Emit(OpCodes.Ldloc_S, i);
                EmitReflectionAccessProvider.BoxIfNeed(parameterType, generator);
                generator.Emit(OpCodes.Stelem_Ref);
            }
            /**/

            generator.Emit(OpCodes.Ret);
            /**/

            return nestedType;
        }

        /// <summary>
        /// Find <see cref="ProxyBase"/> from collection, and cast it to specified <see cref="castType"/>.
        /// </summary>
        /// <param name="ilGenerator"></param>
        /// <param name="proxyField"></param>
        /// <param name="castType"></param>
        /// <param name="token"> </param>
        private static LocalBuilder FindProxy(ILGenerator ilGenerator, FieldInfo proxyField, Type castType, int token)
        {
            LocalBuilder proxy = ilGenerator.DeclareLocal(castType);
            ilGenerator.Emit(OpCodes.Ldarg_0);
            ilGenerator.Emit(OpCodes.Ldfld, proxyField);
            ilGenerator.Emit(OpCodes.Ldc_I4, token);
            ilGenerator.Emit(OpCodes.Callvirt, proxyField.FieldType.GetMethod("Find", new[] {typeof (int)}));
            ilGenerator.Emit(OpCodes.Castclass, castType);
            ilGenerator.Emit(OpCodes.Stloc, proxy);

            return proxy;
        }
       
        private static bool IsHaveMethodAttribue(MethodAttributes methodAttribute, MethodAttributes checkAttribute)
        {
            return (methodAttribute & checkAttribute) == checkAttribute;
        }

        #endregion
    }
}